package org.go.ee.jmx.middle.jboss;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;

import org.go.expcetion.SchedulerConfigException;
import org.go.expcetion.SchedulerException;
import org.go.log.Log;
import org.go.scheduler.Scheduler;
import org.go.scheduler.StdSchedulerFactory;
import org.jboss.naming.NonSerializableFactory;
import org.jboss.system.ServiceMBeanSupport;

/**
 * JBoss specific MBean implementation for configuring, starting, and
 * binding to JNDI a Go Scheduler instance.
 *  
 * <p>
 * Sample MBean deployment descriptor: 
 * <a href="doc-files/quartz-service.xml" type="text/plain">quartz-service.xml</a>
 * </p>
 * 
 * <p>
 * <b>Note:</b> The Scheduler instance bound to JNDI is not Serializable, so 
 * you will get a null reference back if you try to retrieve it from outside
 * the JBoss server in which it was bound.  If you have a need for remote 
 * access to a Scheduler instance you may want to consider using Go's RMI 
 * support instead.  
 * </p>
 *  
 * @see org.go.ee.jmx.middle.jboss.GoJmxServiceMBean
 * 
 * @author hejie
 */
public class GoJmxService extends ServiceMBeanSupport implements GoJmxServiceMBean {
	private Log log;
	private Properties properties;

	private StdSchedulerFactory schedulerFactory;

	private String jndiName;

	private String propertiesFile;

	private boolean error;

	private boolean useProperties;

	private boolean usePropertiesFile;

	/*
	* If true, the scheduler will be started. If false, the scheduler is initailized 
	* (and available) but start() is not called - it will not execute jobs. 
	*/
	private boolean startScheduler = true;

	public GoJmxService() {
		// flag initialization errors
		error = false;

		// use PropertiesFile attribute
		usePropertiesFile = false;
		propertiesFile = "";

		// use Properties attribute
		useProperties = false;
		properties = new Properties();

		// default JNDI name for Scheduler
		jndiName = "GoScheduler";
	}

	@Override
	public void setJndiName(String jndiName) throws Exception {
		String oldName = this.jndiName;
		this.jndiName = jndiName;

		if (super.getState() == STARTED) {
			unbind(oldName);

			try {
				rebind();
			} catch (NamingException ne) {
				log.error("Failed to rebind Scheduler", ne);
				throw new SchedulerConfigException("Failed to rebind Scheduler - ", ne);
			}
		}
	}

	@Override
	public String getJndiName() {
		return jndiName;
	}

	@Override
	public String getName() {
		return "GoService(" + jndiName + ")";
	}

	@Override
	public void setProperties(String properties) {
		if (usePropertiesFile) {
			log.error("Must specify only one of 'Properties' or 'PropertiesFile'");

			error = true;

			return;
		}

		useProperties = true;

		try {
			properties = properties.replace(File.separator, "/");
			ByteArrayInputStream bais = new ByteArrayInputStream(properties.getBytes());
			this.properties = new Properties();
			this.properties.load(bais);
		} catch (IOException ioe) {
			// should not happen
		}
	}

	public String getProperties() {
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			properties.store(baos, "");

			return new String(baos.toByteArray());
		} catch (IOException ioe) {
			// should not happen
			return "";
		}
	}

	@Override
	public void setPropertiesFile(String propertiesFile) {
		if (useProperties) {
			log.error("Must specify only one of 'Properties' or 'PropertiesFile'");

			error = true;

			return;
		}

		usePropertiesFile = true;

		this.propertiesFile = propertiesFile;
	}

	public String getPropertiesFile() {
		return propertiesFile;
	}

	@Override
	public void setStartScheduler(boolean startScheduler) {
		this.startScheduler = startScheduler;
	}

	public boolean getStartScheduler() {
		return startScheduler;
	}

	@Override
	public void createService() throws Exception {
		log.info("Create GoService(" + jndiName + ")...");

		if (error) {
			log.error("Must specify only one of 'Properties' or 'PropertiesFile'");

			throw new Exception("Must specify only one of 'Properties' or 'PropertiesFile'");
		}

		schedulerFactory = new StdSchedulerFactory();

		try {
			if (useProperties) {
				//schedulerFactory.initialize(properties);
			}

			if (usePropertiesFile) {
				//schedulerFactory.initialize(propertiesFile);
			}
		} catch (Exception e) {
			log.error("Failed to initialize Scheduler", e);

			throw new SchedulerConfigException("Failed to initialize Scheduler - ", e);
		}

		log.info("GoService(" + jndiName + ") created.");
	}

	@Override
	public void destroyService() throws Exception {
		log.info("Destroy GoService(" + jndiName + ")...");

		schedulerFactory = null;

		log.info("GoService(" + jndiName + ") destroyed.");
	}

	@Override
	public void startService() throws Exception {
		log.info("Start GoService(" + jndiName + ")...");

		try {
			rebind();
		} catch (NamingException ne) {
			log.error("Failed to rebind Scheduler", ne);
			throw new SchedulerConfigException("Failed to rebind Scheduler - ", ne);
		}

		try {
			Scheduler scheduler = schedulerFactory.getScheduler("");

			if (startScheduler) {
				//scheduler.start();
			} else {
				log.info("Skipping starting the scheduler (will not run jobs).");
			}
		} catch (Exception e) {
			log.error("Failed to start Scheduler", e);

			throw new SchedulerConfigException("Failed to start Scheduler - ", e);
		}

		log.info("QuartzService(" + jndiName + ") started.");
	}

	@Override
	public void stopService() throws Exception {
		log.info("Stop GoService(" + jndiName + ")...");

		try {
			Scheduler scheduler = schedulerFactory.getScheduler("");

			scheduler.shutdown(true);
		} catch (Exception e) {
			log.error("Failed to shutdown Scheduler", e);
			throw new SchedulerConfigException("Failed to shutdown Scheduler - ", e);
		}

		unbind(jndiName);

		log.info("GoService(" + jndiName + ") stopped.");
	}

	private void rebind() throws NamingException, SchedulerException {
		InitialContext rootCtx = null;
		try {
			rootCtx = new InitialContext();
			Name fullName = rootCtx.getNameParser("").parse(jndiName);
			Scheduler scheduler = schedulerFactory.getScheduler("");
			NonSerializableFactory.rebind(fullName, scheduler, true);
		} catch (Exception e) {
			log.error("Failed to shutdown Scheduler", e);
			throw new SchedulerException("Failed to shutdown Scheduler - ", e);
		} finally {
			if (rootCtx != null) {
				try {
					rootCtx.close();
				} catch (NamingException ignore) {
				}
			}
		}
	}

	private void unbind(String name) {
		InitialContext rootCtx = null;
		try {
			rootCtx = new InitialContext();
			rootCtx.unbind(name);
			NonSerializableFactory.unbind(name);
		} catch (NamingException e) {
			log.warn("Failed to unbind scheduler with jndiName: " + name, e);
		} finally {
			if (rootCtx != null) {
				try {
					rootCtx.close();
				} catch (NamingException ignore) {
				}
			}
		}
	}
}
